﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TypeResolve
{
	public static class TypeResolver
	{
		private static readonly Dictionary<Type, string> nameCache = new Dictionary<Type, string>();
		private static readonly Dictionary<string, Type> typeCache = new Dictionary<string, Type>();

		internal static readonly Dictionary<string, Type> predefinedMappings =
			new Dictionary<string, Type>
				{
					{"string", typeof (string)},
					{"int", typeof (int)},
					{"byte", typeof (byte)},
					{"short", typeof (short)},
					{"long", typeof (long)},
					{"bool", typeof (bool)},
					{"Guid", typeof (Guid)},
					{"List`1", typeof (List<>)},
					{"Dictionary`2", typeof (Dictionary<,>)},
					{"DictionaryEntry", typeof (DictionaryEntry)},
					{"Type", typeof (Type)}
				};

		static TypeResolver()
		{
			foreach (var mapping in predefinedMappings)
			{
				nameCache.Add(mapping.Value, mapping.Key);
				typeCache.Add(mapping.Key, mapping.Value);
			}
		}

		public static void AddTypeMapping(Type type, string name)
		{
			nameCache[type] = name;
			typeCache[name] = type;
		}

		public static string TypeToString(Type type)
		{
			return TypeToString(type, null);
		}

		public static string TypeToString(Type type, object arrayInstance)
		{
			//if (type.IsNotPublic)
			//	throw new ArgumentException("Only public types can be serialized! " + type.Name + " is not public."); //this is so that errors because of non-public classes become immediately visible
			string typename = ConvertTypeToString(type);
			if (type.IsArray && arrayInstance != null)
			{
				var array = (Array) arrayInstance;
				for (int i = 0; i < array.Rank; i++)
				{
					typename += (i == 0 ? "=" : ",");
					typename += array.GetLength(i);
				}
			}
			return typename;
		}

		private static string ConvertTypeToString(Type type)
		{
			lock (nameCache)
			{
				if (nameCache.ContainsKey(type))
					return nameCache[type];
				var typename = new StringBuilder("");
				if (type.IsArray)
				{
					typename.Append(ConvertTypeToString(type.GetElementType()));
					typename.Append("[");
					typename.Append(type.GetArrayRank());
					typename.Append("]");
				}
				else if (type.GetTypeInfo().IsGenericType)
				{
					if (type.DeclaringType != null)
					{
						typename.Append(ConvertTypeToString(type.DeclaringType));
						typename.Append("+");
						typename.Append(type.Name.Substring(0, type.Name.Length));
						typename.Append("<");
						typename.Append(string.Join(",", type.GenericTypeArguments.Select(t => ConvertTypeToString(t)).ToArray()));
						typename.Append(">");
					}
					else
					{
						if (!type.GetTypeInfo().IsGenericTypeDefinition)
						{
							var typedef = ConvertTypeToString(type.GetGenericTypeDefinition());
							typename.Append(typedef.Substring(0, typedef.Length - 2));
							typename.Append("<");
							typename.Append(string.Join(",", type.GenericTypeArguments.Select(t => ConvertTypeToString(t)).ToArray()));
							typename.Append(">");
						}
						else
						{
							typename.Append(type.Namespace);
							typename.Append(".");
							typename.Append(type.Name.Substring(0, type.Name.Length));
						}
					}
				}
				else
				{
					if (type.DeclaringType != null)
					{
						typename.Append(ConvertTypeToString(type.DeclaringType));
						typename.Append("+");
						typename.Append(type.Name);
					}
					else
						typename.Append(type.FullName);
				}
				string typenameStr = typename.ToString();
				nameCache.Add(type, typenameStr);
				return typenameStr;
			}
		}

		public static Type Resolve(string typeName)
		{
			int[] arrayDimensions;
			return Resolve(typeName, out arrayDimensions);
		}

		public static Type Resolve(string typeName, out int[] arrayDimensions)
		{
			bool isArrayType = typeName.IndexOf("]=") > -1;
			arrayDimensions = null;
			if (isArrayType)
			{
				string[] split = typeName.Split(new[]{"]="}, StringSplitOptions.None);
				typeName = split[0] + "]";
				arrayDimensions = split[1].Split(',').Select(s => int.Parse(s)).ToArray();
			}

			return ConvertStringToType(typeName);
		}

		private static Type ConvertStringToType(string typeName)
		{
			lock (typeCache)
			{
				//try to find type in local cache for performance
				if (typeCache.ContainsKey(typeName))
					return typeCache[typeName];

				Type result = null;
				bool isArrayType = typeName.EndsWith("]");
				bool isGenericType = typeName.EndsWith(">");
				if (isArrayType)
				{
					Type elementType = ConvertStringToType(typeName.Substring(0, typeName.Length - 3));
					int arrayRank = int.Parse(typeName[typeName.Length - 2].ToString()); //Name structure: typename[x] where x is the array rank (only arrays up to rank 9 supported!)
					result = (arrayRank > 1 ? elementType.MakeArrayType(arrayRank) : elementType.MakeArrayType());
				}
				else if (isGenericType)
				{
					int genStartIdx = typeName.IndexOf('<');
					string genericBaseTypeStr = typeName.Substring(0, genStartIdx);
					string genericArgumentsStr = typeName.Substring(genStartIdx + 1, typeName.Length - genStartIdx - 2);
					var genericArguments = SplitGenericArguments(genericArgumentsStr).Select(s => ConvertStringToType(s)).ToArray();
					bool isNestedType = (genericBaseTypeStr.IndexOf("+") > -1);
					var genericBaseType = isNestedType ? ConvertStringToType(genericBaseTypeStr) : ConvertStringToType(genericBaseTypeStr + "`" + genericArguments.Length);
					result = genericBaseType.MakeGenericType(genericArguments);
				}
				else
					result = GetType(typeName);

				//add found type to local cache
				typeCache.Add(typeName, result);

				return result;
			}
		}

		private static IEnumerable<string> SplitGenericArguments(string args)
		{
			int level = 0;
			int idx = 0;
			int lastIdx = -1;
			foreach (char c in args)
			{
				if (c == '<' || c == '[')
					level++;
				else if (c == '>' || c == ']')
					level--;
				else if (c == ',' && level == 0)
				{
					yield return args.Substring(lastIdx+1, idx - lastIdx - 1);
					lastIdx = idx;
				}
				idx++;
			}
			yield return args.Substring(lastIdx+1, args.Length - lastIdx - 1);
		}

		private static Type GetType(string typeName)
		{
			var result = TypeUtils.SearchType(typeName);
			if (result == null)
				throw new InvalidOperationException(String.Format("Type \"{0}\" could not be found in any loaded assembly", typeName));
			return result;
		}
	}
}
